﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;

using Fuse8.DomainFramework.Domain;
using Fuse8.DomainFramework.Common;

namespace Fuse8.DomainFramework.DataAccess.InMemoryDatabase
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TKey"></typeparam>
    internal class InMemoryDataRepositoryMapper<TEntity, TKey> : IEntityDataRepositoryMapper<TEntity, TEntity>
        where TEntity : class, IEntityObject<TKey>
        where TKey : IComparable<TKey>, IEquatable<TKey> 
    {
        #region IEntityDataRepositoryMapper methods

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="repositoryEntity"></param>
        public void ToRepositoryEntity(TEntity entity, TEntity repositoryEntity)
        {
            FromRepositoryEntity(entity, repositoryEntity);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="repositoryEntity"></param>
        /// <param name="entity"></param>
        public void FromRepositoryEntity(TEntity repositoryEntity, TEntity entity)
        {
            Guard.ArgumentNotNull(repositoryEntity, "repositoryEntity");
            Guard.ArgumentNotNull(entity, "entity");

            var properties = GetProperties(entity);
            object propertyValue = null;
            foreach (var property in properties)
            {
                propertyValue = GetProperty(repositoryEntity, property);
                if (propertyValue != null)
                {
                    SetProperty(entity, property.PropertyName, propertyValue);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public TEntity CreateEntity()
        {
            return CreateRepositoryEntity();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public TEntity CreateRepositoryEntity()
        {
            return Activator.CreateInstance<TEntity>();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="repositoryEntity"></param>
        /// <param name="entity"></param>
        public void SetIdentityField(TEntity repositoryEntity, TEntity entity)
        {
            entity.Id = repositoryEntity.Id;
        }

        #endregion

        #region Private methods

        private static IList<EntityProperty> GetProperties(object source)
        {
            Guard.ArgumentNotNull(source, "source");

            return source.GetType().GetProperties()
                .Where(p => p.CanRead && p.CanWrite)
                .Select(p => new EntityProperty
                {
                    PropertyName = p.Name,
                    PropertyType = p.PropertyType
                }).ToList();
        }

        private static object GetProperty(object source, EntityProperty property)
        {
            Guard.ArgumentNotNull(source, "source");
            Guard.ArgumentNotNull(property, "property");

            PropertyInfo propertyInfo = source.GetType().GetProperty(property.PropertyName);
            if (propertyInfo == null)
                throw new ArgumentException(
                    string.Format(CultureInfo.CurrentUICulture, "Could not find a propertyName with the name '{0}'", property.PropertyName), "property");

            return propertyInfo.GetValue(source, null);
        }

        private static void SetProperty(object source, string propertyName, object propertyValue)
        {
            Guard.ArgumentNotNull(source, "source");
            Guard.ArgumentNotEmpty(propertyName, "propertyName");
            Guard.ArgumentNotNull(propertyValue, "propertyValue");

            PropertyInfo propertyInfo = source.GetType().GetProperty(propertyName);
            if (propertyInfo == null)
                throw new ArgumentException(
                    string.Format(CultureInfo.CurrentUICulture, "Could not find a propertyName with the name '{0}'", propertyName), "propertyName");

            propertyInfo.SetValue(source, propertyValue, null);
        }

        #endregion
    }

    /// <summary>
    /// 
    /// </summary>
    internal class EntityProperty
    {
        /// <summary>
        /// 
        /// </summary>
        public string PropertyName { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public Type PropertyType { get; set; }
    }
}
